﻿/*
BF4Rcon.NET, provides a .NET implementation of BF4's RCON interface.
Copyright (C) 2013 Tim Marro

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using TimScripts.BF4Rcon.Frostbite;
using System.Threading;

namespace TimScripts.BF4Rcon.Test
{
    [TestFixture]
    public class RequestManagerTests
    {
        RequestManager manager;

        [SetUp]
        public void SetUp()
        {
            manager = new RequestManager();
        }

        [Test]
        public async Task GetsResponseAfterAddedAsync()
        {
            int seq = 1;
            Packet response = GetResponsePacket(seq, "wat");
            Packet result;

            manager.AddResponse(response);
            result = await manager.GetResponseAsync(seq);

            Assert.That(result, Is.SameAs(response));
        }

        [Test]
        public void GetsResponseAfterAdded()
        {
            int seq = 1;
            Packet response = GetResponsePacket(seq, "wat");
            Packet result;

            manager.AddResponse(response);
            result = manager.GetResponse(seq);

            Assert.That(result, Is.SameAs(response));
        }

        [Test]
        public async Task GetResponseThenAddAsync()
        {
            int seq = 1;
            Packet response = GetResponsePacket(seq, "wat");
            Packet result;

            Task<Packet> t = manager.GetResponseAsync(seq);
            await Task.Delay(200);
            manager.AddResponse(response);

            result = await t;

            Assert.That(result, Is.SameAs(response));
        }

        [Test]
        public async Task GetResponseThenAdd()
        {
            int seq = 1;
            Packet response = GetResponsePacket(seq, "wat");
            Packet result;
            Task<Packet> t = Task.Factory.StartNew<Packet>(() => manager.GetResponse(seq));

            //await Task.Delay(200);
            manager.AddResponse(response);

            result = await t;

            Assert.That(result, Is.SameAs(response));
        }

        [Test]
        public async Task GetResponeTimesOut()
        {
            Task<Packet> t = Task.Factory.StartNew<Packet>(() => manager.GetResponse(1, 200));

            await Task.Delay(500);

            Assert.Throws<TimeoutException>(async () => await t);
        }

        [Test]
        public void GetResponseAsyncCancel()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Task<Packet> t = manager.GetResponseAsync(1, cts.Token);

            cts.Cancel();

            Assert.Throws<TaskCanceledException>(async () => await t);
        }

        [Test]
        public void ClosingThrowsAsync()
        {
            Task t = manager.GetResponseAsync(1);

            manager.Close(new TestException());

            Assert.Throws<TestException>(async () => await t);
        }

        [Test]
        public async Task ClosingThrows()
        {
            Task<Packet> t = Task.Factory.StartNew<Packet>(() => manager.GetResponse(1));

            await Task.Delay(100);
            manager.Close(new TestException());

            try
            {
                t.Wait();
            }
            catch (AggregateException ex)
            {
                Assert.AreEqual(1, ex.InnerExceptions.Count);
                Assert.That(ex.InnerExceptions[0], Is.TypeOf<TestException>());
            }
        }

        [Test]
        public async Task CloseAllAsync()
        {
            Task t = TaskEx.WhenAll(
                manager.GetResponseAsync(1),
                manager.GetResponseAsync(2),
                manager.GetResponseAsync(3),
                manager.GetResponseAsync(4),
                manager.GetResponseAsync(5)
                );

            manager.Close(new TestException());

            try
            {
                await t;
            }
            catch //either aggregate or taskcanceled
            {
                Assert.AreEqual(5, t.Exception.InnerExceptions.Count);

                foreach (Exception ex in t.Exception.InnerExceptions)
                {
                    Assert.That(ex, Is.TypeOf<TestException>());
                }
            }
        }

        [Test]
        public void CannotGetResponseAfterClose()
        {
            manager.Close(new TestException());
            Assert.Throws<TestException>(() => manager.GetResponse(1));
        }

        [Test]
        public void CannotGetResponseAfterCloseAsync()
        {
            manager.Close(new TestException());
            Assert.Throws<TestException>(async () => await manager.GetResponseAsync(1));
        }

        private static Packet GetResponsePacket(int sequence, params string[] words)
        {
            return new Packet(false, true, sequence, words);
        }

        private class TestException : Exception { }

    }
}
